package cos480.amiwsd;

import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Random;


/**
 * 
 * GenMgmt:
 * 
 * Generation management. This class is responsible for the generation of 
 * landscapes, instances and parameters for the simulation.
 * 
 * @author will, emma
 *
 */


public class GenMgmt {

	// width of imaginary cartesian situation...
	private static final double WIDTH = Math.sqrt(3)/2;
	
	private Random rand;		// random number generator
	private Charset charset;	// charset we want to use for reading and writing
	
	public GenMgmt(){
		rand = new Random();	// initialize the random number generator
		charset = Charset.forName("US-ASCII");	// initialize charset
	}

	/**
	 * Generate a landscape (confidence intervals) using ALL scan data
	 * 
	 * @return PostgreSQL query string
	 */
	public String genLand(Statement sql) {
		
		return "";
	}
	
	/**
	 * Generate a landscape file (confidence intervals) using 
	 * scan data from scans performed on given dates
	 * 
	 * @param dates List of dates of scans from which to generate landscape
	 * @return PostgreSQL query string
	 */
	public String genLand(Statement sql, LinkedList<String> dates){
		
		return "";
	}
	
	/**
	 * Generate a list of parameters for a simulation using the given
	 * parameters. Output to file with given filename, or with default
	 * file name: R[numSims]_[simType]_L[landID]-params
	 * 
	 * Possible parameters:
	 * -- numSims: number of simulations for which to generate parameters
	 * -- filename: filename to use for output file; see above for default
	 * -- mu: value for mu (S->I rate) (DEFAULT: 1)
	 * -- phi: value for phi (I->R rate) (DEFAULT: 100)
	 * -- gamma: value for gamma (R->S rate; only valid for simType=SIRS) (DEFAULT: 3)
	 * -- landID: ID of landscape instance to use (NO DEFAULT)
	 * -- simType: type of simulation (SIR, SIRS, SIS) (NO DEFAULT)
	 * -- minTime: minimum number of time steps to run (DEFAULT: 10)
	 * -- maxTime: maximum number of time steps to run (DEFAULT: 50)
	 * -- I0: initial number of infected individuals (DEFAULT: 100)
	 * -- CRnum: number of Code Red params to generate (DEFAULT: 10)
	 * 
	 * @param sql the PostgreSQL Statement object
	 * @param params parameters to use for the scans
	 * @return PostgreSQL query string
	 * @throws SQLException 
	 */
	public String genSimParams(Connection con, HashMap<String,String> params) throws SQLException{
		int numSims, landID, minTime, maxTime, I0, CRnum, i;
		String filename, simType, statement, writeStr;
		double mu, phi, gamma;
		double[] pVals;
		BufferedWriter writer = null;
		PreparedStatement pStatement;
		
		if(!(params.containsKey("numSims") && params.containsKey("landID") && params.containsKey("simType"))){
			System.err.println("Must specify numSims, landID, and simType to generate simulation parameters.");
			return null;
		}
		
		numSims = Integer.parseInt(params.get("numSims"));
		landID = Integer.parseInt(params.get("landID"));
		simType = params.get("simType");
		
		mu = params.containsKey("mu")? Double.parseDouble(params.get("mu")) : 1;
		phi = params.containsKey("phi")? Double.parseDouble(params.get("phi")) : 100;
		gamma = params.containsKey("gamma")? Double.parseDouble(params.get("gamma")) : 3;
		
		minTime = params.containsKey("minTime")? Integer.parseInt(params.get("minTime")) : 10;
		maxTime = params.containsKey("maxTime")? Integer.parseInt(params.get("maxTime")) : 50;
		I0 = params.containsKey("I0")? Integer.parseInt(params.get("I0")) : 100;
		CRnum = params.containsKey("CRnum")? Integer.parseInt(params.get("CRnum")) : 10;
		
		
		if((filename = params.get("filename")) == null)
			filename = "R" + numSims + "_" + simType + "_L" + landID + "-params";
		
		statement = "insert into simulation(default, ?, ?, ?, "+phi+", "+mu+", "+gamma+", '"+simType;
		statement += "', "+landID+", "+numSims+", "+minTime+", "+maxTime+", "+I0+", "+CRnum+", "+filename+")";
		pStatement = con.prepareStatement(statement);
		
		System.out.println("Generating simulation parameter list to output file: " + filename);
		
		try {
			writer =  new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filename), charset));
		
		// generate random p values
		for(i = 0; i < numSims; i++){
			pVals = genPVals();
			
			// put parameters into database
			pStatement.setDouble(1, pVals[0]);
			pStatement.setDouble(2, pVals[1]);
			pStatement.setDouble(3, pVals[2]);
			
			try {
				pStatement.executeUpdate();
			} catch (SQLException e) {
				// do nothing; want to ignore key collisions
				
//				System.err.println("Unable to insert simulation parameters into database: " + e.getMessage());
//				throw e;
			}
			
			//output parameters to file
			writeStr = "simType: "+simType+"\tsize: 256\tlandFname: "+landID+"\tminTime: "+minTime;
			writeStr += "\tmaxTime: "+maxTime+"\tnumI0: "+I0+"\tmu: "+mu+"\tphi: "+phi+"\tgamma: "+gamma;
			writeStr += "\tpLong: "+pVals[0]+"\tpMed: "+pVals[1]+"\tpShort:"+pVals[2]+"\n";
			writer.write(writeStr);
			
		}
		
		// add Code Red params
		pVals = new double[3];
		pVals[2]=0.375;
		pVals[1]=0.5;
		pVals[0]=0.125;
		
		pStatement.setDouble(1, pVals[0]);
		pStatement.setDouble(2, pVals[1]);
		pStatement.setDouble(3, pVals[2]);
		
		try {
			pStatement.executeUpdate();
		} catch (SQLException e) {
			// do nothing; want to ignore key collisions
			
//			System.err.println("Unable to insert Code Red simulation parameters into database: " + e.getMessage());
//			throw e;
		}
		
		for(i = 0; i < CRnum; i++){
			//output parameters to file
			writeStr = "simType: "+simType+"\tsize: 256\tlandFname: "+landID+"\tminTime: "+minTime;
			writeStr += "\tmaxTime: "+maxTime+"\tnumI0: "+I0+"\tmu: "+mu+"\tphi: "+phi+"\tgamma: "+gamma;
			writeStr += "\tpLong: "+pVals[0]+"\tpMed: "+pVals[1]+"\tpShort:"+pVals[2]+"\n";
			writer.write(writeStr);
		}
		writer.close();
		
		} catch (Exception e) {
		    System.err.println("IOException... failed to open the BufferedWriter to write parameters.");
		}
		return "";
	}
	
	private double[] genPVals(){
		double randCartX = rand.nextDouble();
		double randCartY = rand.nextDouble()*WIDTH;
		if ((randCartX < 0.5) && (randCartY > 2*WIDTH*randCartX)){
		   	randCartX = 0.5 - randCartX;
		   	randCartY = WIDTH-randCartY;
		}
		else if (randCartY > 2*WIDTH*(1-randCartX)){
		   	randCartX = 1.5 - randCartX;
		    randCartY = WIDTH-randCartY;
		}
		return cartesianToBarycentric(randCartX, randCartY, 1, WIDTH);
	}
	
	private double[] cartesianToBarycentric(double cartX, double cartY, double height, double width){
		double[] result = new double[3];
		double d = width * height;
		double x = cartY * width;
		double y = -height * cartX + d - x/2;
		double z = d - x - y;
		result[0] = x/d;
		result[1] = y/d;
		result[2] = z/d;
		return result;
	}
}
